import { Map } from 'immutable';
import validate from './validate';

/**
 * Form field store
 */
class Form {
  /**
   * Constructor
   * @type {Object}
   */
  constructor(opts = {}) {
    this.rules = opts.rules;
    this.initFields = opts.fields || {};

    this.fields = new Map(this.initFields);
  }

  /**
   * Get field value
   *
   * @access {private}
   * @param  {string} field
   * @param  {any} notSetValue
   * @return {object}
   */
  _get = (field, notSetValue) => (
    this.fields.get(field, notSetValue)
  )

  /**
   * Set field value
   *
   * @access {private}
   * @param {string} field
   * @param {function || object} updater
   * @param {any} notSetValue
   */
  _set = (field, updater, notSetValue) => {
    const value = typeof updater === 'function'
      ? updater(this._get(field, notSetValue))
      : updater;

    this.fields = this.fields.set(field, value);
  }

  /**
   * Try get value state
   *
   * @access {private}
   * @param  {Event or value} e
   * @return {string}
   */
  _value = (e) => (
    e && e.target && e.target.value !== undefined
      ? e.target.value
      : e
  )

  /**
   * Try get checked state
   *
   * @access {private}
   * @param  {Event} e
   * @param  {function} failed
   * @return {bool}
   */
  _checked = (e, failed) => (
    e && e.target && e.target.checked !== undefined
      ? e.target.checked
      : failed()
  )

  /**
   * Try get value from Function or Object
   *
   * @access {private}
   * @param  {Function} fn   [description]
   * @param  {[type]}   args [description]
   * @return {[type]}        [description]
   */
  _tryGetValue = (fn, ...args) => {
    if (fn === undefined) return this._value(...args);

    return typeof fn === 'function' ? fn(...args) : fn;
  }


  /**
   * Link to value field
   * @param  {String} field
   * @param  {Object} [opts={}]
   * @return {Object} Props
   */
  field = (field, opts = {}) => ({
    defaultValue: this._get(field),

    onChange: (e) => {
      const value = this._tryGetValue(opts.value, e);

      this._set(field, value);
    },
  })

  /**
   * Link to checkbox list
   * @param  {String} field
   * @param  {Object} [opts={}]
   * @return {Object} Props
   */
  list = (field, opts = {}) => ({
    onChange: (e) => {
      const value = this._tryGetValue(opts.value, e);
      const checked = this._checked(e, () => !this._get(field, []).includes(value));

      if (checked) {
        this._set(field, prev => ([...prev.filter(x => x !== value), value]), []);
      } else {
        this._set(field, prev => prev.filter(x => x !== value), []);
      }
    },
  })

  /**
   * Validate values
   * @return {Object} Invalid fields
   */
  validate = () => {
    const rules = this.rules;
    const fields = this.fields.toJS();

    return validate(fields, rules);
  }

  reset = () => {
    this.fields = new Map(this.initFields);
  }

  /**
   * Get values
   * @return {object}
   */
  values = () => this.fields.toJS()

}

export default Form;
